﻿using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.IO;
using System.Xml;
using AbstraX.Contracts;
using System.Collections;
using Metaspec;
using System.Xml.XPath;
using Microsoft.Build.Construction;
using Microsoft.Build.CommandLine;
using Microsoft.Build.Framework;

namespace VisualStudioProvider
{
    [DebuggerDisplay("{Name}, {RelativePath}, {ProjectType}")]
    public class VSProject : IVSProject, ILogger, IEventSource
    {
        static readonly Type s_ProjectInSolution;
        static readonly Type s_RootElement;
        static readonly Type s_ProjectRootElement;
        static readonly Type s_ProjectRootElementCache;
        static readonly PropertyInfo s_ProjectInSolution_ProjectName;
        static readonly PropertyInfo s_ProjectInSolution_ProjectType;
        static readonly PropertyInfo s_ProjectInSolution_RelativePath;
        static readonly PropertyInfo s_ProjectInSolution_ProjectGuid;
        static readonly PropertyInfo s_ProjectRootElement_Items;

        private VSSolution solution;
        private string projectFileName;
        private object internalSolutionProject;
        private XPathDocument xPathDocument;
        private XmlNamespaceManager nsmgr;
        private IProject iProject;
        private List<VSProjectItem> items;
        private ProjectElementContainer rootElementContainer;
        private ProjectRootElement rootElement;
        private LoggerVerbosity loggerVerbosity = LoggerVerbosity.Minimal;
        private string parameters = string.Empty;

        public event AnyEventHandler AnyEventRaised;
        public event BuildFinishedEventHandler BuildFinished;
        public event BuildStartedEventHandler BuildStarted;
        public event CustomBuildEventHandler CustomEventRaised;
        public event BuildErrorEventHandler ErrorRaised;
        public event BuildMessageEventHandler MessageRaised;
        public event ProjectFinishedEventHandler ProjectFinished;
        public event ProjectStartedEventHandler ProjectStarted;
        public event BuildStatusEventHandler StatusEventRaised;
        public event TargetFinishedEventHandler TargetFinished;
        public event TargetStartedEventHandler TargetStarted;
        public event TaskFinishedEventHandler TaskFinished;
        public event TaskStartedEventHandler TaskStarted;
        public event BuildWarningEventHandler WarningRaised;
        public string Name { get; private set; }
        public string ProjectType { get; private set; }
        public string RelativePath { get; private set; }
        public string ProjectGuid { get; private set; }

        public IProjectRoot ProjectRoot { get; set; }
        public IArchitectureLayer ArchitectureLayer { get; set; }

        public IVSSolution ParentSolution
        {
            get
            {
                return solution;
            }
        }

        public string FileName
        {
            get
            {
                return projectFileName;
            }
        }

        public IProject IProject
        {
            get 
            {
                if (iProject == null)
                {
                    iProject = solution.ISolution.getProjects().SingleOrDefault(p => p.getPath() == this.projectFileName);
                }

                return iProject; 
            }
        }

        public string XPathNamespacePrefix
        {
            get
            {
                return "vs";
            }
        }

        public string DefaultNamespace 
        {
            get
            {
                if (iProject != null && iProject is ICsProject && ((ICsProject)iProject).getNamespace() != null)
                {
                    return ((ICsProject)iProject).getNamespace().name;
                }
                else if (iProject is ICsProject)
                {
                    var iter = this.Select(string.Format("{0}:Project/{0}:PropertyGroup/{0}:RootNamespace", this.XPathNamespacePrefix));

                    iter.MoveNext();

                    return iter.Current.Value;
                }
                else
                {
                    return null;
                }
            }
        }

        static VSProject()
        {
            s_ProjectInSolution = Type.GetType("Microsoft.Build.Construction.ProjectInSolution, Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false, false);

            s_ProjectInSolution_ProjectName = s_ProjectInSolution.GetProperty("ProjectName", BindingFlags.NonPublic | BindingFlags.Instance);
            s_ProjectInSolution_ProjectType = s_ProjectInSolution.GetProperty("ProjectType", BindingFlags.NonPublic | BindingFlags.Instance);
            s_ProjectInSolution_RelativePath = s_ProjectInSolution.GetProperty("RelativePath", BindingFlags.NonPublic | BindingFlags.Instance);
            s_ProjectInSolution_ProjectGuid = s_ProjectInSolution.GetProperty("ProjectGuid", BindingFlags.NonPublic | BindingFlags.Instance);

            s_ProjectRootElement = Type.GetType("Microsoft.Build.Construction.ProjectRootElement, Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false, false);
            s_ProjectRootElementCache = Type.GetType("Microsoft.Build.Evaluation.ProjectRootElementCache, Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false, false);

            s_ProjectRootElement_Items = s_ProjectRootElement.GetProperty("Items", BindingFlags.Public | BindingFlags.Instance);
        }

        public IEnumerable<IVSProjectItem> Items
        {
            get
            {
                return items;
            }
        }

        public void AddCompileFile(string fileNameSource, string relativeTargetPath)
        {
            var fileInfo = new FileInfo(projectFileName);
            var itemGroup = rootElementContainer.Children.Where(e => e is ProjectItemGroupElement)
                .Cast<ProjectItemGroupElement>()
                .Where(p => p.Children.Cast<ProjectItemElement>()
                .Any(i => i.ItemType == "Compile")).Single();

            fileInfo = new FileInfo(Path.Combine(fileInfo.DirectoryName, relativeTargetPath));

            if (!fileInfo.Directory.Exists)
            {
                fileInfo.Directory.Create();
            }

            File.Copy(fileNameSource, fileInfo.FullName);

            itemGroup.AddItem("Compile", fileInfo.FullName);
        }

        public void RemoveItem(VSProjectItem item)
        {
            ProjectElementContainer parent = ((ProjectElement)item.InternalProjectItem).Parent;

            parent.RemoveChild((ProjectElement) item.InternalProjectItem);
        }

        public void Save()
        {
            rootElement.Save(projectFileName);
        }

        public void Compile()
        {
            this.Save();

            MSBuildApp.Execute(this.FileName, null, null, new Dictionary<string, string>() 
                {
                    {"Configuration", "Debug"},
                    {"Platform", "Any CPU"},
                    {"OutputPath", @"bin\Debug"}
                },  new ILogger[] { this }, loggerVerbosity);
        }

        public XPathNodeIterator Select(string xPath)
        {
            if (xPathDocument == null)
            {
                var stream = File.OpenRead(projectFileName);

                stream.Seek(0, SeekOrigin.Begin);

                xPathDocument = new XPathDocument(stream);

                stream.Close();

                nsmgr = new XmlNamespaceManager(new NameTable());
                nsmgr.AddNamespace("vs", "http://schemas.microsoft.com/developer/msbuild/2003");
            }

            return xPathDocument.CreateNavigator().Select(xPath, nsmgr);
        }

        public VSProject(VSSolution solution, object internalSolutionProject)
        {
            this.Name = s_ProjectInSolution_ProjectName.GetValue(internalSolutionProject, null) as string;
            this.ProjectType = s_ProjectInSolution_ProjectType.GetValue(internalSolutionProject, null).ToString();
            this.RelativePath = s_ProjectInSolution_RelativePath.GetValue(internalSolutionProject, null) as string;
            this.ProjectGuid = s_ProjectInSolution_ProjectGuid.GetValue(internalSolutionProject, null) as string;

            this.solution = solution;
            this.internalSolutionProject = internalSolutionProject;

            this.projectFileName = Path.Combine(solution.SolutionPath, this.RelativePath);

            items = new List<VSProjectItem>();

            if (this.ProjectType == "KnownToBeMSBuildFormat")
            {
                this.Parse();
            }
        }

        public VSProject(string fileName)
        {
            projectFileName = fileName;
            items = new List<VSProjectItem>();

            this.Parse();
        }

        private void Parse()
        {
            var stream = File.OpenRead(projectFileName);
            var reader = XmlReader.Create(stream);
            var cache = s_ProjectRootElementCache.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic).First().Invoke(new object[] { true });
            
            rootElement = (ProjectRootElement) s_ProjectRootElement.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic).Where(c => c.GetParameters().Count() == 2 && c.GetParameters().First().ParameterType.Name == "XmlReader" && c.GetParameters().ElementAt(1).ParameterType.Name == "ProjectRootElementCache").First().Invoke(new object[] { reader, cache });
            rootElementContainer = (ProjectElementContainer)rootElement;

            stream.Close();

            var collection = (ICollection)s_ProjectRootElement_Items.GetValue(rootElement, null);

            items.Clear();

            foreach (var item in collection)
            {
                items.Add(new VSProjectItem(this, item));
            }

        }

        public IEnumerable<IVSProjectItem> EDMXModels
        {
            get 
            {
                return items.Where(i => i.ItemType == "EntityDeploy");
            }
        }

        public IEnumerable<IVSProjectItem> Schemas
        {
            get
            {
                var schemas = items.Where(i => i.Name.EndsWith(".xsd") && !i.Include.StartsWith(@"Web References\") && !i.Include.StartsWith(@"Service References\"));

                return schemas;
            }
        }

        public void Dispose()
        {
        }

        public void Initialize(IEventSource eventSource)
        {
            eventSource.AnyEventRaised += (sender, e) =>
            {
                AnyEventRaised(sender, e);
            };

            eventSource.BuildFinished += (sender, e) =>
            {
                BuildFinished(sender, e);
            };

            eventSource.BuildStarted += (sender, e) =>
            {
                BuildStarted(sender, e);
            };

            eventSource.CustomEventRaised += (sender, e) =>
            {
                CustomEventRaised(sender, e);
            };

            eventSource.ErrorRaised += (sender, e) =>
            {
                ErrorRaised(sender, e);
            };

            eventSource.MessageRaised += (sender, e) =>
            {
                MessageRaised(sender, e);
            };

            eventSource.ProjectFinished += (sender, e) =>
            {
                ProjectFinished(sender, e);
            };

            eventSource.ProjectStarted += (sender, e) =>
            {
                ProjectStarted(sender, e);
            };

            eventSource.StatusEventRaised += (sender, e) =>
            {
                StatusEventRaised(sender, e);
            };

            eventSource.TargetFinished += (sender, e) =>
            {
                TargetFinished(sender, e);
            };

            eventSource.TargetStarted += (sender, e) =>
            {
                TargetStarted(sender, e);
            };

            eventSource.TaskFinished += (sender, e) =>
            {
                TaskFinished(sender, e);
            };

            eventSource.TaskStarted += (sender, e) =>
            {
                TaskStarted(sender, e);
            };

            eventSource.WarningRaised += (sender, e) =>
            {
                WarningRaised(sender, e);
            };
        }

        public string Parameters
        {
            get
            {
                return parameters;
            }
            set
            {
                parameters = value;
            }
        }

        public void Shutdown()
        {
        }

        public LoggerVerbosity Verbosity
        {
            get
            {
                return loggerVerbosity;
            }
            set
            {
                loggerVerbosity = value;
            }
        }
    }
}
